#!/usr/bin/python
# -*- coding: UTF-8 -*-
import sys
sys.path.append(r'D:\code\git\zxc\project_format_jzbxh_qdzs\qdzs')
sys.path.append(r'D:\code\git\zxc\project_format_jzbxh_qdzs\qdzs\sengo_common\def_config')
# khj
sys.path.append(r'D:\code\git\project_format_jzbxh_qdzs\qdzs')
sys.path.append(r'D:\code\git\project_format_jzbxh_qdzs\qdzs\sengo_common\def_config')

from logAgent.logManager import Loger, LOG_LEVEL
Loger.GetSingleton().msg('--- 加载: Main_ui ---')
import os
from PyQt5 import QtWidgets
from ClientUi.mycommon import pyqt_utils as utils

import get_config

from PyQt5.QtGui import QColor, QIcon, QPalette, QPixmap, QImage
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QGridLayout, QVBoxLayout, QHBoxLayout, QWidget, \
    QPushButton, QGroupBox, QListWidget, QTextEdit, QMenuBar, QMenu, QListWidgetItem, QTableWidget, QTableWidgetItem, \
    QAbstractItemView, QHeaderView, QFrame, QDesktopWidget, QAction, QSizePolicy, QMessageBox, QDialog, QFileDialog
from PyQt5.QtCore import Qt, QRect, pyqtSignal, QMargins, QSize, QCoreApplication
from sengo_common.gui_utils import *
from ClientUi.MsgPanels import MsgPanels
from ClientUi.controls.ImagePanelsGroupWidget import ImagePanelsGroupWidget
from ClientUi.controls.QMessageBoxEx import QMessageBoxEx
from ClientUi.controls.WaitWidget import WaitDialogWorker
import threading
import time
import traceback
from ClientUi.CoreProcessListener import CoreProcessListener
from get_config import *
from sengo_common.test_gui_database import Ui_DataBase,stat_data, get_data_stat


def search_file(pattern, dirs):
    import glob
    return pattern
    if isinstance(dirs, str):
        dirs = [dirs]
    
    for directory in dirs:
        for file in glob.glob(f'{directory}/**/*{pattern}*', recursive=True):
            return file

    return pattern




class MainWindow(QMainWindow):
    __AppVersion = '1.0.1'
    #notify_list = pyqtSignal(list)
    # 后 --> 前 通讯接口约定
    showSignal = pyqtSignal(list)
    changeSignal = pyqtSignal(list)
    scadaSignal = pyqtSignal(list)
    hand_saomaSignal = pyqtSignal(list)
    hand_querenSignal = pyqtSignal(list, list)

    # # special
    # showPnSnSignal = pyqtSignal(list)  # 显示pnsn

    
    __printLogSignal = pyqtSignal(str, LOG_LEVEL)
    __dbgui = None
    __train = None
    yoloapi = None
    __procCore = None
    __devStatusCheckThread = None
    __algSet = None
    imgPanelsGroup = None
    __infoPanels = None
    # __IsShowStatistics = True   # 是否显示右侧统计栏
    # __IsShowDevices = True      # 是否显示右侧设备栏
    __IsShowStatistics = False   # 是否显示右侧统计栏
    __IsShowDevices = False      # 是否显示右侧设备栏
    __IsShowLogs = True         # 是否显示右侧日志栏

    __IsExitingApp = False

    __listener = None

    __STR_STATISTICS = '统计栏'
    __STR_DEVICE = '设备栏'
    __STR_LOG = '日志栏'

    def __init__(self, ss=None):
        super(QMainWindow, self).__init__()

        self.ss = ss
        self.__uiThreadId = threading.get_ident()
        #self.notify_list.connect(self.on_notify_list)
        self.__printLogSignal.connect(self.__printLog)
        self.__listener = CoreProcessListener()
        self.__listener.RegShowImgCb(self._OnShowImgData)
        self.__listener.RegSend2PlcCb(self._OnSend2Plc)
        
        self.showSignal.connect(self.show_fun)
        self.changeSignal.connect(self.change_fun)
        self.scadaSignal.connect(self.scadaSignal_fun)
        #self.hand_saomaSignal.connect(self.hand_saoma_fun)
        #self.hand_querenSignal.connect(self.hand_queren_fun)
        # self.showPnSnSignal.connect(self.showPnSn_fun)


        self.initUI()

    def loginfo(self, msg):
        self.__printLog(msg)
    
    def set_runing(self, b):
        return 0
    
    def stop(self):
        if self.ss is None:
            return 0
        self.ss.stop()
        self.set_runing(False)
        print('stop')
        self.loginfo('start')

    def __reInitCameraBoxes(self):
        #ncams = len(camnames)
        #print(f'{ncams}')
        #max_camera_num = len(config.get('ips', []))
        #ncams = max_camera_num
        ncams = config.get('cameranum', 0)
        if self.imgPanelsGroup is None:
            self.imgPanelsGroup = ImagePanelsGroupWidget(ncams)
        else:
            if self.__lastCameraNum != ncams:
                self.imgPanelsGroup.SetShowImgNum(ncams)
        self.__lastCameraNum = ncams
        for i in range(ncams):
            name = config.get(f"camera{i + 1}_name", '')
            self.imgPanelsGroup.SetImgBoxTitleByIndex(i, name)
        return self.imgPanelsGroup
    
    def initUI(self):
        self.msgqq = queue.Queue()
        current_path = os.path.abspath(os.getcwd())
        print(f'#### 当前路径:{current_path}')
        #加载图片资源
        self.__startBtnIcon = QIcon(search_file('images\\start.png', './'))
        self.__stoptBtnIcon = QIcon(search_file('images\\stop.png', './'))
        self.__setBtnIcon = QIcon(search_file('images\\set.png', './'))
        self.__resetBtnIcon = QIcon(search_file('images\\reset.png', './'))
        self.setWindowIcon(QIcon(search_file('images\\icon.ico', './')))  # 设置窗口图标
        # self.setWindowTitle(f'AI 标准软件  -  v{self.__AppVersion}')
        self.setWindowTitle(f'AI 标准软件  -  v{self.__AppVersion}')
        screen = QDesktopWidget().screenGeometry()
        print(screen.getRect())
        mar = QMargins(200, 100, 200, 100)
        newRct = screen.marginsRemoved(mar)
        self.setGeometry(newRct)

        # Create the menu bar
        self.__createMenu()

        main_widget = QWidget()
        self.setCentralWidget(main_widget)

        main_layout = QVBoxLayout(main_widget)
        self.__main_layout = main_layout

        # Start, Settings, and Alarm Reset buttons
        toolbar = self.__createToolbarWidget()
        main_layout.addWidget(toolbar)
        main_layout.setContentsMargins(0,0,0,0)
        main_layout.setSpacing(3)

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(1, 0, 1, 0)
        hLayout.setSpacing(3)
        imgbox = self.__reInitCameraBoxes()
        self.__infoPanels = MsgPanels()
        self.__infoPanels.panelHideEvent.connect(self.__clickBtnHide)
        hLayout.addWidget(imgbox)
        hLayout.addWidget(self.__infoPanels)
        main_layout.addLayout(hLayout)
        self.__infoPanels.HideWidget(0, not self.__IsShowStatistics)
        self.__infoPanels.HideWidget(1, not self.__IsShowDevices)
        self.__infoPanels.HideWidget(2, not self.__IsShowLogs)

        Loger.GetSingleton().bindPrintCallback(self.__printLog)  # 绑定打印日志的回调函数

        Loger.GetSingleton().msg('UI界面创建完成')
        #self.__SetTitleBarColor()

        self.__reInitDevsList()

        # 创建线程，并传递参数
        self.__devStatusCheckThread = threading.Thread(target=self.dev_chek, args=(1,1))
        # 启动线程
        self.__devStatusCheckThread.start()

    def __ping(self, ip: str):
        import subprocess
        result = subprocess.run(['ping', '-n', '1', '-w', '100', ip], stdout=subprocess.PIPE)
        if "TTL=" in result.stdout.decode('utf-8', errors='ignore'):
            return True
        return False

    def dev_chek(self,sleep, cilrcle):
        return 0
        while get_config.devcircle[0]:
            num = 100
            while num>0:
                time.sleep(sleep/100)
                num -= 1
                if self.__IsExitingApp:
                    break
            if self.__IsExitingApp:
                break
            devs = []
            try:
                num = config['cameranum']
                for i in range(num):
                    ip = config[f"camera{i + 1}_devinfo"]
                    result = self.__ping(ip)
                    devs.append((ip, result))
            except Exception as e:
                pass

            try:
                num = config['used_plc_num']
                for i in range(num):
                    ip = config[f"plc{i + 1}_ipaddress"]
                    result = self.__ping(ip)
                    devs.append((ip, result))
            except Exception as e:
                pass

            try:
                num = config['used_scanner_num']
                for i in range(num):
                    name = config[f"scanner{i + 1}_name"]
                    if config[f"scanner{i + 1}_type"] == 0:
                        ip = config[f"{i + 1}_ipaddress"]
                        result = self.__ping(ip)
                        devs.append((ip, result))
                    else:
                        pass
            except Exception as e:
                pass

            self.updateDevStatus(devs)

    def __showAlgSetDlg(self):
        pass
        # if self.__algSet is None:
        #     from ClientUi.algorithmFlowCfg.AlgSetDlg import AlgSetDlg
        #     self.__algSet = AlgSetDlg()
        # self.__algSet.show()

    def openprojdir(self):
        # startfile(out_img_p)
        # os.startfile(self.ui_config['save_dir'])
        os.startfile(r'D:\data\save\jzbxh')
        return 0
    
    # def __showAlgSetDlg(self):
    #     password = config.get('password', '')
    #     def check_pass():
    #         text, ok = QInputDialog.getText(
    #             self, '密码输入框', '请输入密码：', QLineEdit.Password)
    #         if not ok:
    #             return 0
    #         if text != password:
    #             reply = QMessageBox.question(self, '信息', '密码错误',
    #                                          QMessageBox.Yes)
    #             self.loginfo('密码错误')
    #             return 0
    #         return 1
    #     if len(password)==0 or check_pass():
    #         self.setting.exec_()
    #         # config_save()
    #         #self.stat_pad.update_ui()
    #         self.loginfo('设置成功')
    #         return 1
    #
    #     return 0
    
    def __clickAlarmReset(self):
        Loger.GetSingleton().msg('点击了按钮【报警复位】')

    def _OnShowImgData(self, li: list):
        index, numpy_image, im1, tt, xx, t0, cnt0 = li
        Loger.GetSingleton().msg(f'_OnShowImgData index:{index}, thread id:{threading.get_ident()}')
        _, name = xx

        if not get_config.webcampic[3]:
            self.updateImgAndResultByIndex(index-1, im1, 'OK')
        if get_config.webcampic[3]:
            get_config.webcampic[3] = False
            self.updateImgAndResultByIndex(index-1, im1, 'OK')
            stxt = f'相机{index}产品不良'
            QMessageBox.critical(None, '注意', stxt,
                                 QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        get_config.datastatic_ng[index][1] += 1
        self.updateStatistics(('OK', get_config.datastatic_ng[index][1], get_config.datastatic_ng[index][0],
                               (get_config.datastatic_ng[index][1] - get_config.datastatic_ng[index][0]) * 100 / get_config.datastatic_ng[index][1]))

    def _OnSend2Plc(self, regAddr, data):
        print('Main ui, 给PLC发送数据')

    def __SetTitleBarColor(self):
        palette = QPalette()
        palette.setColor(QPalette.WindowText, Qt.white)  
        palette.setColor(QPalette.Window, QColor(53, 53, 53))  
        self.setPalette(palette)

    def __createMenu(self):
        menubar = self.menuBar()
        # 文件菜单
        fileMenu = menubar.addMenu('文件')
        subMenuExport = QAction('导出项目', self)
        subMenuExport.triggered.connect(self.menuEventExport)
        subMenuImport = QAction('导入项目', self)
        subMenuImport.triggered.connect(self.menuEventImport)
        subMenuExit = QAction('退出', self)
        subMenuExit.triggered.connect(self.menuEventExit)
        fileMenu.addAction(subMenuExport)
        fileMenu.addAction(subMenuImport)
        fileMenu.addAction(subMenuExit)

        # 视图菜单
        viewMenu = menubar.addMenu('视图')
        subMenuStatistics = QAction(self.__STR_STATISTICS, self, checkable=True)
        subMenuStatistics.triggered.connect(self.menuEventStatistics)
        subMenuStatistics.setChecked(self.__IsShowStatistics)
        subMenuDevices = QAction(self.__STR_DEVICE, self, checkable=True)
        subMenuDevices.triggered.connect(self.menuEventDevices)
        subMenuDevices.setChecked(self.__IsShowDevices)
        subMenuLogs = QAction(self.__STR_LOG, self, checkable=True)
        subMenuLogs.triggered.connect(self.menuEventLogs)
        subMenuLogs.setChecked(self.__IsShowLogs)
        viewMenu.addAction(subMenuStatistics)
        viewMenu.addAction(subMenuDevices)
        viewMenu.addAction(subMenuLogs)
        self.__subMenuStatistics = subMenuStatistics
        self.__subMenuDevices = subMenuDevices
        self.__subMenuLogs = subMenuLogs

        # 设备管理菜单
        deviceMenu = menubar.addMenu('设备管理')
        subMenuCameraManager = QAction('相机管理', self)
        subMenuCameraManager.triggered.connect(self.menuEventCameraManager)
        subMenuPlcManager = QAction('PLC管理', self)
        subMenuPlcManager.triggered.connect(self.menuEventPlcManager)
        subMenuScannerManager = QAction('扫码枪管理', self)
        subMenuScannerManager.triggered.connect(self.menuEventScannerManager)
        deviceMenu.addAction(subMenuCameraManager)
        deviceMenu.addAction(subMenuPlcManager)
        deviceMenu.addAction(subMenuScannerManager)
        self.__deviceMenu = deviceMenu

        # 设置菜单
        settingsMenu = menubar.addMenu('设置')
        subMenuProjectConf = QAction('流程设置', self)
        subMenuProjectConf.setIcon(self.__setBtnIcon)
        subMenuProjectConf.triggered.connect(self.menuEventProjectConf)
        settingsMenu.addAction(subMenuProjectConf)
        self.__settingsMenu = settingsMenu

        # 工具菜单
        toolsMenu = menubar.addMenu('工具')
        subMenuImgDebug = QAction('图像调试', self)
        subMenuImgDebug.triggered.connect(self.menuEventImgDebug)
        toolsMenu.addAction(subMenuImgDebug)
        # 数据查询
        subMenuDataBase = QAction('数据查询', self)
        subMenuDataBase.triggered.connect(self.menuEventDataBase)
        toolsMenu.addAction(subMenuDataBase)
        # 模型训练
        subMenuTrain = QAction('模型训练', self)
        subMenuTrain.triggered.connect(self.menuEventTrain)
        toolsMenu.addAction(subMenuTrain)
        self.__toolsMenu = toolsMenu

        # 以下为调试UI功能代码
        if sys.gettrace() is not None and 0:
            uidebugMenu = menubar.addMenu('UI 界面效果调试')
            subMenuItemUidebug = QAction('测试', self)
            subMenuItemUidebug.triggered.connect(self.menuEventUiTest)
            uidebugMenu.addAction(subMenuItemUidebug)


    def menuEventExport(self):
        Loger.GetSingleton().msg('点击了菜单：导出')
        filename, _ = QFileDialog.getSaveFileName(self, '导出项目文件', '', '配置文件(*.json)')
        if filename:
            print('导出文件:', filename)
            try:
                get_config.config_save(filename)
                QMessageBoxEx.informationEx('提示', '导出项目文件成功', search_file('images\\icon.ico', './'))
            except Exception as e:
                QMessageBoxEx.warningEx('警告', '导出项目文件失败', search_file('images\\icon.ico', './'))


    def menuEventImport(self):
        Loger.GetSingleton().msg('点击了菜单：导入')
        filename, _ = QFileDialog.getOpenFileName(self, '导入项目文件', '', '配置文件(*.json)')
        if filename:
            print('导入文件:', filename)
            config.update(get_config.config_load(filename))
            self.__reInitCameraBoxes()
            self.__reInitDevsList()

    def menuEventExit(self):
        Loger.GetSingleton().msg('点击了菜单：退出')
        self.close()

    def menuEventStatistics(self):
        Loger.GetSingleton().msg('点击了菜单：统计栏')
        self.__IsShowStatistics = not self.__IsShowStatistics
        self.__subMenuStatistics.setChecked(self.__IsShowStatistics)
        self.__infoPanels.HideWidget(0, not self.__IsShowStatistics)

    def menuEventDevices(self):
        Loger.GetSingleton().msg('点击了菜单：设备栏')
        self.__IsShowDevices = not self.__IsShowDevices
        self.__subMenuDevices.setChecked(self.__IsShowDevices)
        self.__infoPanels.HideWidget(1, not self.__IsShowDevices)

    def menuEventLogs(self):
        Loger.GetSingleton().msg('点击了菜单：日志栏')
        self.__IsShowLogs = not self.__IsShowLogs
        self.__subMenuLogs.setChecked(self.__IsShowLogs)
        self.__infoPanels.HideWidget(2, not self.__IsShowLogs)

    def menuEventCameraManager(self):
        Loger.GetSingleton().msg('点击了菜单：相机管理')
        from ClientUi.deviceManager.cameraSettingDlg import cameraSettingDlg
        window = cameraSettingDlg()
        ret = window.exec_()
        if ret == QDialog.Accepted:
            self.__reInitCameraBoxes()
            self.__reInitDevsList()

    def menuEventPlcManager(self):
        Loger.GetSingleton().msg('点击了菜单：PLC管理')
        from ClientUi.deviceManager.plcSettingDlg import plcSettingDlg
        window = plcSettingDlg()
        ret = window.exec_()
        if ret == QDialog.Accepted:
            self.__reInitDevsList()

    def menuEventScannerManager(self):
        Loger.GetSingleton().msg('点击了菜单：扫码枪管理')
        from ClientUi.deviceManager.scannerSettingDlg import scannerSettingDlg
        window = scannerSettingDlg()
        ret = window.exec_()
        if ret == QDialog.Accepted:
            self.__reInitDevsList()

    def menuEventProjectConf(self):
        Loger.GetSingleton().msg('点击了菜单：流程设置')
        self.__showAlgSetDlg()

    def menuEventImgDebug(self):
        Loger.GetSingleton().msg('点击了菜单：图像调试')
        from ClientUi.tools.ImageCaptureDlg import ImageCaptureDlg
        imgCapDlg = ImageCaptureDlg()
        imgCapDlg.exec_()

    def menuEventDataBase(self):
        Loger.GetSingleton().msg('点击了菜单：数据查询')
        if self.__dbgui is None:
            #from ClientUi.tools.queryDatabaseDlg import Ui_DataBase
            #from sengo_common.test_gui_database import test_gui_database
            self.__dbgui = Ui_DataBase(self.__procCore)
            
        self.__dbgui.showMaximized()
        #self.__dbgui.show()

    def menuEventTrain(self):
        Loger.GetSingleton().msg('点击了菜单：模型训练')
        if self.__train is None:
            #from ClientUi.tools.queryDatabaseDlg import Ui_DataBase
            #from sengo_common.test_gui_database import test_gui_database
            #from sengo_common.test_gui_database import Ui_DataBase
            if self.yoloapi is None:
                QMessageBoxEx.informationEx('警告', '不支持训练', search_file('images\\icon.ico', './'))
                return 0
            from sengo_common.easytrain.test_train_gui import EasyTrainer
            self.__train = EasyTrainer(self.yoloapi)
            # window.setCentralWidget(widget)
            # window.resize(900, 600)
            self.__train.setWindowTitle('模型训练')
            self.__train.setWindowIcon(QIcon('logo.ico'))
            self.__train.resize(830, 800)
            self.__train.show()
        #self.__train.showMaximized()
        self.__train.show()

    def __createToolbarWidget(self):
        panel = QWidget()
        panel.setObjectName('PanelToolbar')
        top_button_layout = QHBoxLayout()
        self.start_button = QPushButton('复位', self)
        self.start_button.setIcon(self.__startBtnIcon)
        self.settings_button = QPushButton('设置', self)
        self.settings_button.setIcon(self.__setBtnIcon)
        self.settings_button.clicked.connect(self.__showAlgSetDlg)
        # self.openprojdir_button = QPushButton('打开项目目录', self)
        self.openprojdir_button = QPushButton('打开图片目录', self)
        self.openprojdir_button.setIcon(self.__setBtnIcon)
        self.openprojdir_button.clicked.connect(self.openprojdir)

        # 另外添加
        self.manual_trigger_button = QPushButton('手动触发', self) # 另外添加
        self.manual_trigger_button.setIcon(self.__setBtnIcon)
        self.manual_trigger_button.clicked.connect(self.manual_trigger_slot)

        # self.dianjian_button = QPushButton('点检', self) # 另外添加
        # self.dianjian_button.setIcon(self.__setBtnIcon)
        # self.dianjian_button.clicked.connect(self.dianjian_slot)

        self.pn_input_box = QLineEdit(self)# 创建输入框 接收pnsn
        self.pn_input_box.setFixedWidth(300) # px
        self.pn_input_box.setPlaceholderText("Enter something and press Enter")
        self.pn_input_box.returnPressed.connect(self.on_enter_pressed_pn_input_box_slot)  # 连接回车键信号到槽函数

        self.show_pnsn_label = QLabel(self) # 显示pnsn
        self.showStatus_label = QLabel(self)  # 显示点检状态等
        # self.show_pnsn_label.setFont(QFont('Arial', 16))  # 设置字体和大小
        # self.show_pnsn_label.setStyleSheet('margin-top: 10px; margin-bottom: 10px;')  # 设置上下间距

        # self.reset_button = QPushButton('报警复位', self)
        # self.reset_button.setObjectName('reset_button')
        # self.reset_button.setIcon(self.__resetBtnIcon)
        top_button_layout.addWidget(self.start_button)
        top_button_layout.addWidget(self.settings_button)
        top_button_layout.addWidget(self.openprojdir_button)
        top_button_layout.addWidget(self.manual_trigger_button)
        # top_button_layout.addWidget(self.dianjian_button) # 不使用
        top_button_layout.addWidget(self.showStatus_label)
        top_button_layout.addStretch()
        top_button_layout.addWidget(self.show_pnsn_label)
        top_button_layout.addWidget(self.pn_input_box)
        # top_button_layout.addWidget(self.reset_button)
        panel.setLayout(top_button_layout)
        panel.setMaximumHeight(50)
        self.setting = Ui_Setting()
        self.start_button.clicked.connect(self.__clickStartOrStop)
        # self.reset_button.clicked.connect(self.__clickAlarmReset)
        return panel

    def __clickBtnHide(self, title: str):
        if title == self.__STR_STATISTICS:
            self.__IsShowStatistics = False
            self.__subMenuStatistics.setChecked(False)
        elif title == self.__STR_DEVICE:
            self.__IsShowDevices = False
            self.__subMenuDevices.setChecked(False)
        elif title == self.__STR_LOG:
            self.__IsShowLogs = False
            self.__subMenuLogs.setChecked(False)

        Loger.GetSingleton().msg(f'隐藏 {title}')

    # 更新设备列表及设备状态
    # 第一个参数为设备名称, 第二个参数为IP或串口号, 第三个参数为连接状态
    # devs = [['camera - 1', '192.168.1.1', True], ['scanner - 1', 'COM1', False]]
    def updateDevsList(self, devs):
        try:
            self.__infoPanels.SetDevsList(devs)
        except Exception as e:
            print(sys.exc_info())


    # 根据IP或者串口号更新设备状态（只更新状态，不修改列表项）
    # devs = [['192.168.1.1', True], ['COM1', True]]
    def updateDevStatus(self, devs):
        self.__infoPanels.updateDevStatus(devs)

    # 清除设备栏
    def clearDevList(self):
        self.__infoPanels.ClearDevList()


    # 更新统计信息
    # 测试用例：self.updateStatistics(('OK', 987654, 12345, 99.765))
    def updateStatistics(self, stats):
        self.__infoPanels.SetStatistics(stats)

    # 清除统计面板上的数据信息
    def clearStatistics(self):
        self.__infoPanels.ClearStatistics()

    # 更新图像上的标题栏
    def updateImgBoxTitleByIndex(self, index, title):
        self.imgPanelsGroup.SetImgBoxTitleByIndex(index, title)

    # 更新图像及结果
    def updateImgAndResultByIndex(self, index, img, result):
        pixmap = None
        if isinstance(img, QPixmap):
            pixmap = img
        else:
            size = self.imgPanelsGroup.GetImgLabelSize(index)
            pixmap = utils.convert2Pixmap(img, size.width(), size.height())
        bOk = result == 'OK'
        self.imgPanelsGroup.SetImgAndResultByIndex(index, pixmap, bOk)

    # 清除图像及结果信息
    def clearImgAndResultByIndex(self, index):
        self.imgPanelsGroup.ClearImgAndResultByIndex(index)

    def __disableWhenStart(self, bDisable: bool):
        # 点击【开始】按钮后，相关功能要从UI上禁用
        self.__deviceMenu.setEnabled(not bDisable)
        self.__settingsMenu.setEnabled(not bDisable)
        self.__toolsMenu.setEnabled(not bDisable)

    def __initCore(self):
        if self.__procCore is None:
            Loger.GetSingleton().msg(f'初始化核心部件')
            from CoreProcess import CoreProcess
            self.__procCore = CoreProcess()
            self.__procCore.SetListener(self.__listener)
            # 第一次调用start非常耗时，所以将start也防止此处
            Loger.GetSingleton().msg(f'首次启动核心部件')
            self.__camret = self.__procCore.start()

    def update_dev(self, b):
        config['running'] = b
        ncams = config.get('cameranum', 0)
        for i in range(ncams):
            config[f"camera{i + 1}_state"] = b
        self.__reInitDevsList()

    def start(self):
        self.start_button.setText('停止')
        self.start_button.setIcon(self.__stoptBtnIcon)
        #self.settings_button.setEnabled(False)
        self.update_dev(True)

        if self.ss is None:
            return 0
        
        self.set_runing(True)
        camret = self.ss.start(self)
        print('start')
        self.loginfo('start')

        #self.__disableWhenStart(True)
        if camret == 1:
            #self.__disableWhenStart(False)
            self.start_button.setText('开始')
            self.start_button.setIcon(self.__startBtnIcon)
            QMessageBoxEx.informationEx('警告', '无法连接相机', search_file('images\\icon.ico', './'))
            return
        print('start')
        return 0
    
    def stop(self):
        self.update_dev(False)
        if self.ss is None:
            return 0
        #self.__disableWhenStart(False)
        self.start_button.setText('开始')
        self.start_button.setIcon(self.__startBtnIcon)
        #self.settings_button.setEnabled(True)
        self.set_runing(True)
        camret = self.ss.stop()
        print('stop')
        self.loginfo('stop')
        
    def __clickStartOrStop(self):
        Loger.GetSingleton().msg(f'点击按钮【{self.start_button.text()}】')

        # 复位逻辑 20250507
        self.ss.reset()
        # isFirstCall = False
        # if 0:
        #     if self.__procCore is None:
        #         isFirstCall = True
        #         w = WaitDialogWorker(lambda : self.__initCore())
        #         w.start()
        #
        # if not config.get('running', False):
        #     self.start()
        # else:
        #     self.stop()

    def __printLog(self, msg, logLevel = LOG_LEVEL.MSG):
        thread_id = threading.get_ident()
        if self.__uiThreadId == thread_id:
            # 当前线程打印日志直接调用
            # print(f'打印日志:{msg}, {logLevel}')
            color = QColor('lightgray')
            if logLevel == LOG_LEVEL.DEBUG:
                color = QColor('green')
            elif logLevel == LOG_LEVEL.MSG:
                color = QColor('lightgray')
            elif logLevel == LOG_LEVEL.WARN:
                color = QColor('orange')
            else :
                color = QColor('red')
            self.__infoPanels.AppendLog(msg, color)
        else:
            # 非主线程打印日志 (未测试)
            # print(f'*********** 跨线程打印日志:{msg}, {logLevel}')
            self.__printLogSignal.emit(msg, logLevel)

    # 清除日志栏
    def __clearLog(self):
        self.__infoPanels.ClearLog()

    def __reInitDevsList(self):
        # 更新设备栏（添加设备和状态）
        devs = []
        # devs = [['camera - 1', True], ['camera - 2', False], ['camera - 3', True], ['plc - 1', False]]
        try:
            num = config['cameranum']
            for i in range(num):
                name = config.get(f"camera{i + 1}_name", '')
                ip = config.get(f"camera{i + 1}_devinfo", '')
                state = config.get(f"camera{i + 1}_state", False)
                devs.append([name, ip, state])
        except Exception as e:
            Loger.GetSingleton().warn(sys.exc_info())

        try:
            num = config['used_plc_num']
            for i in range(num):
                name = config[f"plc{i + 1}_name"]
                ip = config[f"plc{i + 1}_ipaddress"]
                state = config.get(f"plc{i + 1}_state", False)
                devs.append([name, ip, state])
        except Exception as e:
            Loger.GetSingleton().warn(sys.exc_info())

        try:
            num = config['used_scanner_num']
            for i in range(num):
                name = config[f"scanner{i + 1}_name"]
                if config[f"scanner{i + 1}_type"] == 0:
                    ip = config[f"scanner{i + 1}_ipaddress"]
                    state = config.get(f"scanner{i + 1}_state", False)
                    devs.append([name, ip, state])
                else:
                    serial = config[f"scanner{i + 1}_serialport_num"]
                    devs.append([name, serial])
        except Exception as e:
            Loger.GetSingleton().warn(sys.exc_info())

        self.updateDevsList(devs)

    def closeEvent(self, event):
        response = QMessageBoxEx.questionEx('退出', '你确定要退出程序吗？', search_file('images\\icon.ico', './'))
        if response == QMessageBox.Yes:
            # get_config.devcircle[0] = False
            # get_config.run_flag[0] = False
            self.__IsExitingApp = True
            if self.__procCore is not None:
                self.__procCore.stop()
            if self.__devStatusCheckThread is not None:
                self.__devStatusCheckThread.join(1.5)
            event.accept()
            self.ss.stop() # 20250106
        else:
            event.ignore()

    # 以下为UI界面刷新的测试代码
    def menuEventUiTest(self):
        try:
            if self.__testCase == 0:
                self.__testCase = 1
            else:
                self.__testCase = 0
        except Exception as e:
            self.__testCase = 1
        self.testUpdateUiFuncs(self.__testCase)
    def testUpdateUiFuncs(self, case):
        self.__reInitCameraBoxes()
        Loger.GetSingleton().msg(f'测试case:{case}')
        if case == 1:
            # 更新统计栏
            self.updateStatistics(('OK', 987654, 12345, 99.765))
            # 更新设备栏（添加设备和状态）
            devs = [['camera - 1', '192.168.1.1', False], ['camera - 2', '192.168.1.2', False], ['扫码枪', 'COM1', True], ['plc - 1', '192.168.1.10', False]]
            self.updateDevsList(devs)
            # 更新设备栏内设备状态（仅根据设备名称来更新设备的状态）
            devs = [['192.168.1.1', True], ['192.168.1.10', True]]
            self.updateDevStatus(devs)

            # 更新图像控件图片及结果
            testJpg = search_file('test.jpg', './')
            self.updateImgAndResultByIndex(0, QPixmap(testJpg), 'OK')
            self.updateImgAndResultByIndex(1, QPixmap(testJpg), 'NG')
            self.updateImgAndResultByIndex(2, QPixmap(testJpg), 'OK')
            self.updateImgAndResultByIndex(3, QPixmap(testJpg), 'NG')
            # 更新图像控件标题
            self.updateImgBoxTitleByIndex(0, '192.168.1.100')
            self.updateImgBoxTitleByIndex(1, '192.168.1.101')
            self.updateImgBoxTitleByIndex(2, '192.168.1.102')
            self.updateImgBoxTitleByIndex(3, '192.168.1.103')
        else:
            # 清除图像
            self.clearImgAndResultByIndex(0)
            self.clearImgAndResultByIndex(1)
            self.clearImgAndResultByIndex(2)
            self.clearImgAndResultByIndex(3)
            # 清除统计信息
            self.clearStatistics()
            # 清除设备栏中的设备
            self.clearDevList()

    def scadaSignal_fun_impl(self, li):
        msg = f'{li}'
        self.loginfo(msg)
        #self.stat_pad.update_tongji(li)
        if self.msgqq.qsize()==0 and 1:
            self.msgqq.put(1)
            if 0:
                reply = QtWidgets.QMessageBox.question(self, '警告', msg, QtWidgets.QMessageBox.Yes,
                                                        QtWidgets.QMessageBox.No)
            else:
                self.bigmsgbox('警告', msg)
            self.msgqq.get()
        
        return 0
    
    def timer_msg_fun(self):
        if self.in_hand_saoma_lock==1:
            return 0
        if self.msg_qq.qsize()>0:
            li = self.msg_qq.get()
            self.scadaSignal_fun(li)
        
        return 0
        
    def scadaSignal_fun(self, li):
        if self.in_hand_saoma_lock==1:
            self.msg_qq.put(li)
            return 0
        self.scadaSignal_fun_impl(li)
        #Loger.GetSingleton().msg('UI界面创建完成')
        return 0

    def show_fun(self, li):
        # print("get single:",data)return
        index, numpy_image, im1, end, ends, save_img = li
        #self.loginfo(f'show_fun:{[index, end, ends]}')
        cam = self.imgPanelsGroup.imgBoxs[index].camera_image
        
        if im1 is None:
            showimg(cam, numpy_image)
        else:
            showimg(cam, im1)
        return 0

    def change_fun(self, li):
        stat = stat_data.get('stat')
        if li[0] in [0, 1]:
            stat[1] += 1
            if li[0]:
                stat[0] = 'NG'
            else:
                stat[0] = 'OK'
                stat[2] += 1
            stat[3] = 100*stat[2]/max(stat[1], 1)

        self.updateStatistics(stat)
        # self.loginfo(f'{li}')
        self.loginfo(f'{li[0]}')
        return 0
        self.stat_pad.update_tongji(li)
        self.datastt.retranslateUi()
        self.datastt.update()

    # def showPnSn_fun(self):
    #     pass




# import ClientUi.res._res




# def load_qss(app):
#     # 加载QSS文件
#     try:
#         fn = 'styleSheet.qss'
#         fn = search_file('styleSheet.qss', ['./', r'./ui/ClientUi/res','./res',r'D:\code\git\zxc\project_format_jzbxh\ui\ClientUi\res'])
#         # print(os.curdir)
#         fn = 'ui/ClientUi/res/styleSheet.qss'
#         with open(fn, 'r', encoding='utf-8') as qssfile:
#             content = qssfile.read()
#             print('read qss file ok')
#             app.setStyleSheet(content)
#             Loger.GetSingleton().msg('加载QSS完成')
#     except Exception as e:
#         print('read qss file error')
#         Loger.GetSingleton().error(f'加载QSS失败 {e} {traceback.format_exc()}')

from PyQt5.QtCore import QFile, QTextStream

def load_qss(app,qss_fn=':/styleSheet.qss'):
    # 加载QSS文件
    try:
        # fn = ':/styleSheet.qss'
        fn = qss_fn
        qss_file = QFile(fn)
        if qss_file.open(QFile.ReadOnly):
            print('read qss file ok')
            stream = QTextStream(qss_file)
            content = stream.readAll()
            app.setStyleSheet(content)
            qss_file.close()

        Loger.GetSingleton().msg('加载QSS完成')
    except Exception as e:
        print(traceback.format_exc())

        Loger.GetSingleton().error(f'加载QSS失败 {e} {traceback.format_exc()}')

def main():
    Loger.GetSingleton().msg('创建APP')
    app = QApplication(sys.argv)
    load_qss(app)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())


def test_gui_base(ss = None, yoloapi=None):
    app = QApplication(sys.argv) #
    load_qss(app)
    config['running'] = False
    print(f'stat_data len: {len(stat_data)}')

    ex = MainWindow(ss) #
    ex.yoloapi = yoloapi
    
    rets = stat_data.get('data', [])
    okppp = [x for x in rets if get_data_stat(x) == 'OK']
    stat_data['stat'] = ['OK', len(rets), len(okppp), 100*len(okppp)/max(len(rets), 1)]
    ex.updateStatistics(stat_data['stat'])

    #ex.use_pass = 0
    #ex.setWindowTitle(config['classname'])
    ex.setWindowTitle(config.get('checkname', '视觉检测'))
    ex.showMaximized()
    ex.show() #
    if 'start' in sys.argv:
        ex.start()
    
    #ex.bigmsgbox('111', '222')
    #ex.bigmsgbox('1111', '2222')
    #ex.bigmsgbox('11111', '22222')
    if ss is None:
        # updateStatistics
        ex.change_fun([1, '3131315'])
        if 0:
            qq = queue.Queue()
            from concurrent.futures import ThreadPoolExecutor
            pool = ThreadPoolExecutor()
            def test_hand_saoma_main(ex, pool):
                ex.hand_saomaSignal.emit([qq])
                ex.scadaSignal.emit(['adfasdf', '1111'])
                #ex.scadaSignal_fun(['adfasdf', '1111'])
                #ex.scadaSignal.emit(['adfasdf', '1111'])
                return 0
            ret = pool.submit(test_hand_saoma_main, ex, pool)
            #ex.hand_saoma_fun([qq])
    else:
        #ex.start()
        pass
    #ex.scadaSignal.emit(['asdfasdf'])
    #ex = Ui_Setting()
    sys.exit(app.exec_()) #
    return 0

if __name__ == "__main__":
    test_gui_base()
    #main()
